import asyncio
import logging
import random
import os
import json
import hashlib
from playwright.async_api import async_playwright, Browser, Page, Playwright, BrowserContext, Error, Route

from playwright_stealth import stealth_async

logger = logging.getLogger(__name__)

class BrowserManager:
    """
    管理一个单一的、持久化的浏览器实例。
    v33.0 "最终完美寻路版": 增加多路径搜索原生Chrome的能力，并保持最终的伪装和拦截逻辑。
    """
    def __init__(self, user_data_dir: str, target_url: str):
        self.user_data_dir = user_data_dir
        self.main_site_url = "https://aifreeforever.com/"
        self.target_url = target_url
        self.playwright: Playwright = None
        self.context: BrowserContext = None
        self.page: Page = None
        self._lock = asyncio.Lock()
        self.challenge_solved_event = asyncio.Event()
        # 挑战缓存 - 禁用缓存
        self.challenge_cache = {}
        self.cache_file_path = os.path.join(user_data_dir, "vercel_challenge_cache.json")
        # 不加载缓存，每次都是新的挑战
        # self._load_challenge_cache()
        # 标记是否已经成功验证过
        self.is_verified = False

    def _load_challenge_cache(self):
        """加载挑战缓存"""
        try:
            if os.path.exists(self.cache_file_path):
                with open(self.cache_file_path, 'r') as f:
                    self.challenge_cache = json.load(f)
                logger.info(f"已加载 {len(self.challenge_cache)} 条挑战缓存")
        except Exception as e:
            logger.error(f"加载挑战缓存失败: {e}")
            self.challenge_cache = {}

    def _save_challenge_cache(self):
        """保存挑战缓存"""
        try:
            with open(self.cache_file_path, 'w') as f:
                json.dump(self.challenge_cache, f)
        except Exception as e:
            logger.error(f"保存挑战缓存失败: {e}")

    def _get_cache_key(self, request):
        """生成缓存键"""
        # 基于URL和关键请求头生成缓存键
        key_data = {
            'url': request.url,
            'user_agent': request.headers.get('user-agent', ''),
            'origin': request.headers.get('origin', ''),
        }
        key_str = json.dumps(key_data, sort_keys=True)
        return hashlib.md5(key_str.encode()).hexdigest()

    async def _setup_network_logging(self):
        """
        设置网络拦截。只监听挑战请求，不拦截，让浏览器自然处理。
        """
        async def handle_route(route: Route):
            request = route.request
            
            # 对于挑战资源文件，继续正常请求
            if "challenge.v2.min.js" in request.url or "challenge.v2.wasm" in request.url:
                logger.info(f"### 获取挑战资源 [{request.url}] ###")
                await route.continue_()
                return
            
            # 对于挑战请求，只监听不拦截
            if "request-challenge" in request.url and request.method == 'POST':
                logger.info(f"### 监听到挑战请求 [POST {request.url}] ###")
                logger.info(f"请求包含解决方案: {'x-vercel-challenge-solution' in request.headers}")
                
                # 让请求自然进行，不拦截
                try:
                    # 使用continue_让请求正常进行
                    await route.continue_()
                    
                    # 等待一小段时间，然后检查是否成功
                    await asyncio.sleep(0.5)
                    
                    # 如果请求包含解决方案，设置成功标志
                    if 'x-vercel-challenge-solution' in request.headers:
                        logger.info("★★★ 检测到挑战解决方案，预计即将成功！ ★★★")
                        # 延迟设置事件，给浏览器时间处理
                        asyncio.create_task(self._delayed_challenge_success())
                except Exception as e:
                    logger.error(f"处理挑战请求时出错: {e}")
                    await route.continue_()
                return
            
            # 处理403响应，包含挑战token
            if request.url == self.target_url and route.request.method == 'GET':
                try:
                    response = await route.fetch()
                    if response.status == 403 and 'x-vercel-challenge-token' in response.headers:
                        logger.info("### 检测到403挑战响应，等待挑战完成... ###")
                        challenge_token = response.headers.get('x-vercel-challenge-token')
                        logger.info(f"挑战Token: {challenge_token}")
                        
                        # 返回响应
                        await route.fulfill(
                            status=response.status,
                            headers=response.headers,
                            body=await response.body()
                        )
                        return
                except Exception as e:
                    logger.error(f"处理403响应时出错: {e}")
            
            # 其他请求正常继续
            await route.continue_()

        if self.page and not self.page.is_closed():
            await self.page.route("**/*", handle_route)

    async def _delayed_challenge_success(self):
        """延迟设置挑战成功标志，给浏览器时间处理"""
        await asyncio.sleep(2)  # 等待2秒让浏览器处理
        if not self.challenge_solved_event.is_set():
            logger.info("★★★ 设置挑战成功标志！ ★★★")
            self.challenge_solved_event.set()
            self.is_verified = True  # 标记为已验证

    async def initialize(self):
        self.playwright = await async_playwright().start()

        # ==================== 终极寻路策略 ====================
        executable_path = None
        # 检查多个可能的Chrome安装路径
        chrome_paths = [
            "/usr/bin/google-chrome-stable",
            "/usr/bin/google-chrome",
            "/opt/google/chrome/chrome"
        ]
        for path in chrome_paths:
            if os.path.exists(path):
                executable_path = path
                logger.info(f"检测到系统中的 Google Chrome，将使用它启动: {executable_path}")
                break
        
        if not executable_path:
            logger.warning("未在标准路径检测到系统原生Chrome，将回退到Playwright自带的Chromium。")
        # =======================================================

        user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
        extra_http_headers = {
            'sec-ch-ua': '"Google Chrome";v="141", "Not?A_Brand";v="8", "Chromium";v="141"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'cache-control': 'max-age=0',
            'upgrade-insecure-requests': '1',
        }

        self.context = await self.playwright.chromium.launch_persistent_context(
            self.user_data_dir,
            headless=True,
            executable_path=executable_path,
            user_agent=user_agent,
            extra_http_headers=extra_http_headers,
            args=[
                '--no-sandbox', '--disable-setuid-sandbox', '--disable-infobars',
                '--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled',
                '--disable-extensions',  # 禁用扩展避免干扰
                '--disable-plugins',  # 禁用插件
                '--disable-images',  # 禁用图片加载
                # '--disable-javascript',  # 不禁用JS，让挑战正常执行
            ],
            ignore_https_errors=True,
        )
        
        self.page = self.context.pages[0] if self.context.pages else await self.context.new_page()
        await stealth_async(self.page)
        logger.info("'stealth' 补丁已应用。")
        
        await self._setup_network_logging()
        await self.ensure_session_valid()

    async def ensure_session_valid(self):
        # 如果已经验证过，直接返回
        if self.is_verified:
            logger.info("会话已验证，跳过验证流程")
            return
            
        try:
            logger.info(f"正在导航到 {self.target_url} 以检查会话有效性...")
            await self.page.goto(self.target_url, wait_until="domcontentloaded", timeout=30000)
            
            # 检查页面状态
            page_title = await self.page.title()
            page_content = await self.page.content()
            
            # 如果页面正常（不是403或安全验证页面），则认为会话有效
            if "Security Checkpoint" not in page_title and "403" not in page_content:
                try:
                    await self.page.wait_for_selector('textarea[placeholder="Say something..."]', state="visible", timeout=5000)
                    logger.info("会话有效，无需重新验证。")
                    self.is_verified = True
                    return
                except Exception:
                    # 如果找不到输入框，可能页面还在加载
                    pass
            
            # 如果遇到403或安全验证页面，执行验证流程
            logger.warning("当前会话无效或已过期，开始执行\"真人轨迹\"验证流程...")
            await self._perform_verification()
            
        except Exception as e:
            logger.error(f"检查会话有效性时出错: {e}")
            logger.warning("开始执行\"真人轨迹\"验证流程...")
            await self._perform_verification()

    async def _perform_verification(self):
        max_retries = 2
        for attempt in range(max_retries):
            try:
                self.challenge_solved_event.clear()
                logger.info(f"--- 开始第 {attempt + 1}/{max_retries} 次验证尝试 ---")
                
                logger.info(f"第一步：访问主站 {self.main_site_url} 进行预热...")
                await self.page.goto(self.main_site_url, wait_until="domcontentloaded", timeout=30000)
                await asyncio.sleep(random.uniform(2, 4))

                logger.info(f"第二步：跳转到目标聊天页 {self.target_url} ...")
                await self.page.goto(self.target_url, wait_until="domcontentloaded", timeout=30000)
                
                # 等待页面加载完成
                await asyncio.sleep(5)
                
                page_title = await self.page.title()
                logger.info(f"已到达目标页面，当前标题: '{page_title}'.")

                if "Security Checkpoint" in page_title or "403" in await self.page.content():
                    logger.info("### 检测到安全验证页面，等待挑战自动完成... ###")
                    try:
                        logger.info("等待挑战成功信号（最长60秒）...")
                        await asyncio.wait_for(self.challenge_solved_event.wait(), timeout=60)
                        logger.info("已收到挑战成功信号！等待浏览器自动跳转...")
                        
                        # 不手动重新请求，等待浏览器自动跳转
                        logger.info("等待浏览器自动刷新页面...")
                        
                        # 等待页面标题变化
                        try:
                            await self.page.wait_for_function(
                                """() => {
                                    return !document.title.includes('Security Checkpoint') && 
                                           !document.body.innerText.includes('403');
                                }""",
                                timeout=20000  # 等待20秒
                            )
                            logger.info("检测到页面已自动跳转！")
                        except Exception:
                            logger.warning("等待页面自动跳转超时")
                            # 检查当前页面状态
                            page_title = await self.page.title()
                            page_content = await self.page.content()
                            if "Security Checkpoint" not in page_title and "403" not in page_content:
                                logger.info("页面已成功跳转！")
                            else:
                                logger.warning("页面可能仍在安全验证页面")
                                # 最后尝试手动刷新
                                logger.info("尝试手动刷新页面...")
                                await self.page.reload(wait_until="domcontentloaded", timeout=30000)
                                await asyncio.sleep(3)
                        
                        # 最终检查
                        page_title = await self.page.title()
                        page_content = await self.page.content()
                        logger.info(f"最终页面标题: '{page_title}'")
                        
                        if "Security Checkpoint" not in page_title and "403" not in page_content:
                            logger.info("验证成功！")
                            self.is_verified = True  # 标记为已验证
                        else:
                            logger.warning("验证可能未完全成功")
                    except asyncio.TimeoutError:
                        logger.error("等待挑战超时")
                        raise Exception("Vercel Challenge Timeout")
                else:
                    # 如果没有遇到安全验证页面，直接标记为已验证
                    logger.info("未遇到安全验证页面，会话有效")
                    self.is_verified = True
                
                logger.info("第三步：最终确认，等待聊天输入框加载...")
                await self.page.wait_for_selector(
                    'textarea[placeholder="Say something..."]', 
                    state="visible", 
                    timeout=10000
                )
                logger.info(f"--- 第 {attempt + 1} 次验证尝试成功！---")
                return

            except Exception as e:
                logger.critical(f"--- 第 {attempt + 1} 次验证尝试失败！---")
                if attempt == max_retries - 1:
                    logger.critical("所有验证尝试均已失败，程序将退出。")
                    raise e
                else:
                    logger.info("将在5秒后进行下一次尝试...")
                    await asyncio.sleep(5)

    async def get_page(self) -> Page:
        await self._lock.acquire()
        try:
            if self.page.is_closed(): raise Error("共享页面已关闭。")
            await self.page.evaluate("1")
        except Exception as e:
            self._lock.release()
            raise Error(f"获取共享页面时发现其已失效: {e}")
        return self.page

    def release_page(self, page: Page):
        if self._lock.locked(): self._lock.release()

    async def shutdown(self):
        if self.context: await self.context.close()
        if self.playwright: await self.playwright.stop()
        logger.info("浏览器管理器已关闭。")
